/*****************************************************
*
* RODRIGO LUIS DE SOUZA DA SILVA            01/11/2007
*
******************************************************/
/*
A idéia deste projeto código é demonstrar o uso de
múltiplos fragment shaders simultaneamente.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <GL/glew.h>
#include <GL/glut.h>

#include "textfile.h"
#include "Noise3DTexture.h"

///////////////////////////////////////////////////////////////////////////////////////
// Globals
float rotSun = 0.0, rotationX = 0.0, rotationY = 0.0;
int   last_x, last_y;
int width = 800, height = 600;

GLuint noiseVS, sunFS, cloudFS, marbleFS, graniteFS, woodFS;
GLuint noiseProg, sunProg, cloudProg, marbleProg, graniteProg, woodProg;

///////////////////////////////////////////////////////////////////////////////////////
// Functions
GLint getUniLoc(GLuint program, const GLchar *name)
{
    GLint loc;

    loc = glGetUniformLocation(program, name);
    if (loc == -1)
        printf("No such uniform named \"%s\"\n", name);
    return loc;
}

//
void installShader(char *fileShader, int shaderType, GLuint *progID, GLuint *shaderID)
{
	const char * constshader = textFileRead(fileShader);
	// Creating shader program
	*progID   = glCreateProgram();
	// Creating shader
	*shaderID = glCreateShader(shaderType);
	// Passing source code
	glShaderSource(*shaderID, 1, &constshader, NULL);
	// Compiling Shader
	glCompileShader(*shaderID);
}

void attachAndLinkShaders(GLuint progID, GLuint vsShaderID, GLuint fsShaderID)
{
	// Attach Shaders
	glAttachShader(progID, vsShaderID);
	glAttachShader(progID, fsShaderID);
	// Link the program object and print out the info log
	glLinkProgram(progID);
}

// Setting uniform variables
void setUniformVariables(GLuint progID, GLuint vsShaderID, GLuint fsShaderID)
{
	// Vertex Shader Uniforms (neste exemplo tem apenas um vertex shader)
	if(vsShaderID == noiseVS)
	{
	   glUniform1f(getUniLoc(progID, "Scale"), 1.5);
	  	glUniform3f(getUniLoc(progID, "LightPos"), 1.0, 1.0, 1.0);
	   glUniform1f(getUniLoc(progID, "Noise"), 0);
   }

   // Fragment Shaders Uniform
	if(fsShaderID == cloudFS)
	{
	   glUniform1f(getUniLoc(progID, "Scale"), 0.1);
	   glUniform3f(getUniLoc(progID, "SkyColor"), 0.1, 0.1, 0.1);
	   glUniform3f(getUniLoc(progID, "CloudColor"),0.8, 0.8, 0.8);
	}
	if(fsShaderID == sunFS)
	{
	   glUniform3f(getUniLoc(progID,   "Color1"), 0.8, 0.7, 0.0);
	   glUniform3f(getUniLoc(progID,   "Color2"), 0.6, 0.1, 0.8);
	   glUniform1f(getUniLoc(progID,   "NoiseScale"), 1.2);
	}
	if(fsShaderID == marbleFS)
	{
	   glUniform3f(getUniLoc(progID, "MarbleColor"), 0.6, 0.7, 0.6);
	   glUniform3f(getUniLoc(progID, "VeinColor"), 0.1, 0.1, 0.1);
	}
	if(fsShaderID == graniteFS)
	{
	   glUniform1f(getUniLoc(progID, "NoiseScale"), 1.2);
	}
	if(fsShaderID == woodFS)
	{
	   glUniform3f(getUniLoc(progID, "LightWood"), 0.6, 0.3, 0.1);
	   glUniform3f(getUniLoc(progID, "DarkWood"), 0.4, 0.2, 0.07);
	   glUniform3f(getUniLoc(progID, "NoiseScale"), 0.5, 0.1, 0.1);
	   glUniform1f(getUniLoc(progID, "RingFreq"), 4.0);
	   glUniform1f(getUniLoc(progID, "LightGrains"), 1.0);
	   glUniform1f(getUniLoc(progID, "DarkGrains"), 0.0);
	   glUniform1f(getUniLoc(progID, "GrainThreshold"), 0.5);
	   glUniform1f(getUniLoc(progID, "Noisiness"), 3.0);
	   glUniform1f(getUniLoc(progID, "GrainScale"), 27.0);
	   glUniform1f(getUniLoc(progID, "Scale"), 2.0);
	   glUniform3f(getUniLoc(progID, "LightPos"), 0.0,0.0,0.4);
	}
}

void changeSize(int w, int h)
{
	// Prevent a divide by zero, when window is too short
	// (you cant make a window of zero width).
	if(h == 0) h = 1;

	// Set the viewport to be the entire window
   glViewport(0, 0, w, h);

   width = w;
   height = h;
}

void drawFPS()
{
  static char fpsBuf[128] = {0};
  static int frames = 0;
  static int t0 = 0;
  frames++;
  GLint t = glutGet(GLUT_ELAPSED_TIME);
  if (t - t0 >= 1000) {
    GLfloat seconds = (t - t0) / 1000.0;
    GLfloat fps = frames / seconds;
    sprintf(fpsBuf, "FPS: %.1f - Noise Shaders - ESC to exit - ", fps);
    t0 = t;
    frames = 0;
  }
  glutSetWindowTitle(fpsBuf);
}

void renderScene(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Reset the coordinate system before modifying
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// Set the correct perspective.
	gluPerspective(30, (float) width/ (float) height,1,1000);
	glMatrixMode(GL_MODELVIEW);

	glLoadIdentity();
	gluLookAt(0.0,4.0,9.0,  0.0,0.0,-1.0,  0.0f,1.0f,0.0f);

	// Install program object as part of current state
	glUseProgram(cloudProg);
   setUniformVariables(cloudProg, noiseVS, cloudFS);
	glBegin(GL_QUADS);
		float size = 20.0f;
		glNormal3f(0.0f, 0.0f, 1.0f);
		glVertex3f(-size, -size, -size);
		glVertex3f(-size,  size, -size);
		glVertex3f( size,  size, -size);
		glVertex3f( size, -size, -size);
	glEnd();
	glUseProgram(0);


	// Marble base
	glUseProgram(marbleProg);
   setUniformVariables(marbleProg, noiseVS, marbleFS);
	glPushMatrix();
	   glRotatef( rotationY, 0.0, 1.0, 0.0 );
  		glRotatef( rotationX, 1.0, 0.0, 0.0 );
		glTranslatef(0.0f, -0.6f, 0.0f);
  		glScalef(4.0f, -0.2f, 4.0f);
		glutSolidCube(1);
	glPopMatrix();
	glUseProgram(0);

	// Granite Teapot
	glUseProgram(graniteProg);
   setUniformVariables(graniteProg, noiseVS, graniteFS);
	glPushMatrix();
	   glRotatef( rotationY, 0.0, 1.0, 0.0 );
  		glRotatef( rotationX, 1.0, 0.0, 0.0 );
  		glTranslatef(0.5f, 0.2f, 0.5f);
	   glRotatef( -35, 0.0, 1.0, 0.0 );
		glutSolidTeapot(1);
	glPopMatrix();
	glUseProgram(0);

	// Wood Box
	glUseProgram(woodProg);
   setUniformVariables(woodProg, noiseVS, woodFS);
	glPushMatrix();
	   glRotatef( rotationY, 0.0, 1.0, 0.0 );
  		glRotatef( rotationX, 1.0, 0.0, 0.0 );
  		glTranslatef(-1.0f, 0.0, -1.0f);
	   glRotatef( 25, 0.0, 1.0, 0.0 );
		glutSolidCube(1);
	glPopMatrix();
	glUseProgram(0);

	// Sun
	glUseProgram(sunProg);
   setUniformVariables(sunProg, noiseVS, sunFS);
	glPushMatrix();
		glTranslatef(-4.5f, 1.5f, -7.0f);
		rotSun+=0.2f;
		if(rotSun > 360) rotSun = 0.2f;
		glRotatef( rotSun, 0,1,0);
		glutSolidSphere(0.5f, 20, 20);
	glPopMatrix();
	glUseProgram(0);

	drawFPS();
	glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int x, int y)
{
	if (key == 27) exit(0);
}

// Motion callback
void motion(int x, int y )
{
  rotationX += (float) (y - last_y);
  rotationY += (float) (x - last_x);

  last_x = x;
  last_y = y;
}

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowPosition(100,100);
	glutInitWindowSize(width,height);
	glutCreateWindow("Noise Shaders - ESC to exit");

   glutMotionFunc( motion );
	glutDisplayFunc(renderScene);
	glutIdleFunc(renderScene);
	glutReshapeFunc(changeSize);
	glutKeyboardFunc(processNormalKeys);

	glEnable(GL_DEPTH_TEST);
	glClearColor(1.0,1.0,1.0,1.0);

	glewInit();
	if (glewIsSupported("GL_VERSION_2_0"))
		printf("Ready for OpenGL 2.0\n");
	else {
		printf("OpenGL 2.0 not supported\n");
		exit(1);
	}

	// Creating noise 3D
	CreateNoise3D();

	// Installing Vertex Shaders
	installShader((char *) "../projetos/shaderNoise2/shaders/noise.vert", GL_VERTEX_SHADER, &noiseProg, &noiseVS);

	// Installing Fragment Shaders
	installShader((char *) "../projetos/shaderNoise2/shaders/cloud.frag", 	GL_FRAGMENT_SHADER, &cloudProg, 	&cloudFS);
	installShader((char *) "../projetos/shaderNoise2/shaders/sun.frag",   	GL_FRAGMENT_SHADER, &sunProg, 	&sunFS);
	installShader((char *) "../projetos/shaderNoise2/shaders/granite.frag", GL_FRAGMENT_SHADER, &graniteProg,&graniteFS);
	installShader((char *) "../projetos/shaderNoise2/shaders/marble.frag", 	GL_FRAGMENT_SHADER, &marbleProg, &marbleFS);
	installShader((char *) "../projetos/shaderNoise2/shaders/wood.frag", 	GL_FRAGMENT_SHADER, &woodProg, 	&woodFS);

	// Attaching and linking vertex and fragment shaders into a program
	attachAndLinkShaders(cloudProg, 	noiseVS, 	cloudFS);
	attachAndLinkShaders(sunProg, 	noiseVS, 	sunFS);
	attachAndLinkShaders(graniteProg,noiseVS, 	graniteFS);
	attachAndLinkShaders(marbleProg, noiseVS, 	marbleFS);
	attachAndLinkShaders(woodProg, 	noiseVS, 	woodFS);

	glutMainLoop();

	// Removing program
	glDeleteProgram(noiseProg);
	glDeleteProgram(cloudProg);
	glDeleteProgram(sunProg);
	glDeleteProgram(graniteProg);
	glDeleteProgram(marbleProg);
	glDeleteProgram(woodProg);

	return 0;
}
